package TablaDeSimbolos;

import java.util.ArrayList;
import java.util.Hashtable;

/**
 * This class implements the Symbols Table needed by any compiler.
 * It manages different kind of code scopes and reserved words.
 */
public class TablaDeSimbolos{
	/* Todos ellos son tablas Hash con un String como clave y
	 * una lista de atributos polimórficos (en los que se incluye
	 * la propia clave, ya que es un dato más a tener en cuenta).
	 */ 
	/** A table for reserved words.*/
	private Hashtable<String, ArrayList<Object>> 			Palabras_Reservadas;
	/** A list of tables, one table for each scope within the code. */
	private ArrayList<Hashtable<String, ArrayList<Object>>> ListaAmbitos;
	/** A table that relates each scope with it's parent. The main scope doesn't
	 *  have a parent, i.e. it's related to itself. */
	private Hashtable<Integer, Integer> 					TablaOrganizador;
	/** A pointer to the actual scope in ListaAmbitos. */
	private Integer 										PunteroAmbitoActual;
	/** A flag that tells if new insertions are allowed */
	private boolean											blnInsercionesPermitidas;

	/**
	 * Default constructor.
	 * Initializes all the resources needed later on and opens the main scope.
	 */
	public TablaDeSimbolos(){
		// Inicializa el flag de Inserciones Permitidas
		blnInsercionesPermitidas = true; //TODO ESTO ES FALSE, PERO CUANDO SE ANADA EL SINTACTICO
		// Inicializa las tablas
		Palabras_Reservadas	= new Hashtable<String, ArrayList<Object>>();
		ListaAmbitos		= new ArrayList<Hashtable<String,ArrayList<Object>>>();
		TablaOrganizador	= new Hashtable<Integer, Integer>();
		// Actualiza el puntero a dicho ámbito
		PunteroAmbitoActual = new Integer(0);
		// Abre el primer ámbito
		this.AbreAmbito();
		// Añade las palabras reservadas a las tablas
		this.generaListaReservadas();
	}
	
	/**
	 * Initializes the reserved words table from the PalabrasReservadas class.
	 */
	private void generaListaReservadas(){
		PalabrasReservadas[] reserved = PalabrasReservadas.values();
		for (PalabrasReservadas word : reserved ) {
			ArrayList<Object> atributos = new ArrayList<Object>();
			//atributos.add(new Integer(word.ordinal()));
			atributos.add(new String(word.toString()));
			Palabras_Reservadas.put(word.toString(), atributos);
		}
	}
	
	/**
	 * Searches for a given word in the actual scope. It returns the scope range 
	 * number where the identifier is located, or -1 if the word is not found.
	 * 
	 *  @param strIdent identifier String to look for
	 *  @return	<li>the number in the <i>scopes table</i> of the scope that contains
	 *  		the variable
	 *  		<li>-1 if the identifier is not defined
	 *  @see #esReservada(String)
	 *  @see #insertaIdentificador(String)
	 */
	private int estaInsertada(String strIdent){
		Hashtable<String, ArrayList<Object>> ambito;
		int iAmbito = this.PunteroAmbitoActual;
		ambito = ListaAmbitos.get(this.PunteroAmbitoActual);
		// Busca por el ámbito actual y los padres hasta dar con la solución, de existir
		while (ambito != null){
			if (ambito.containsKey(strIdent)){ // Retorna cierto y actualiza el valor
				iAmbito = ListaAmbitos.indexOf(ambito);
				return iAmbito;
			}
			else { // Vuelve al ámbito padre
				if (iAmbito == 0){ // No hay más ámbitos padre
					ambito = null;
					iAmbito = -1;
				}
				else { // Actualiza al ámbito padre
					iAmbito = TablaOrganizador.get(iAmbito);
					ambito = ListaAmbitos.get(TablaOrganizador.get(this.PunteroAmbitoActual));
				}
			}
		}
		return -1;
	}
	
	/**
	 * Searches for a word in the reserved words table. 
	 * 
	 *  @param strIdent identifier to look for.
	 *  @return true if the given String is a reserved word, false otherwise.
	 *  @see #estaInsertada(String) 
	 */
	public boolean esReservada(String strIdent){
		return Palabras_Reservadas.containsKey(strIdent.toUpperCase());
	}
	
	public ArrayList<Object> getReservada(String strIdent){
		return Palabras_Reservadas.get(strIdent.toUpperCase());
	}
	
	/**
	 * Tries to insert a new identifier in the table.
	 * 
	 *  @param strIdent identifier to insert.
	 *  @return	<li>a pointer to the new entry
	 *  		<li>a pointer to the previous entry, if the identifier 
	 *  		was already defined or was a reserved word
	 *  		<li>a pointer to <i>null</i> if no insertions are allowed 
	 *  
	 */
	public ArrayList<Object> insertaIdentificador(String strIdent){
		Integer iAmbitoTemporal;
		iAmbitoTemporal = new Integer(0);
		// Si es una palabra reservada retorna el puntero a la tabla interna de la misma.
		if (esReservada(strIdent)){
			return (Palabras_Reservadas.get(strIdent.toUpperCase()));
		}
		// Si ya existe se devuelve un puntero a la entrada previamente generada.
		if (estaInsertada(strIdent)>0){
			return ListaAmbitos.get(iAmbitoTemporal).get(strIdent); 
		}
		// Si no se permiten inserciones retorna null
		if (!this.blnInsercionesPermitidas) return null;
		// Si no es reservada ni existe previamente se inserta y se devuelve 
		// un puntero a dicha entrada.
		ArrayList<Object> atributos = new ArrayList<Object>();
		atributos.add(new String(strIdent));
		ListaAmbitos.get(this.PunteroAmbitoActual).put(strIdent, atributos);
		// Retorna el puntero a la entrada insertada
		return ListaAmbitos.get(this.PunteroAmbitoActual).get(strIdent);
	}
	
	/**
	 * Adds a new scope range table and updates all related resources.
	 * @see #CierraAmbito()
	 */
	public void AbreAmbito(){
		Hashtable<String, ArrayList<Object>> tempTable;
		// Añade la entrada al organizador
		TablaOrganizador.put(new Integer(ListaAmbitos.size()), new Integer(this.PunteroAmbitoActual));
		// Inserta la tabla en la lista dinámica
		tempTable = new Hashtable<String, ArrayList<Object>>();
		ListaAmbitos.add(tempTable);
		// Actualiza el puntero de ámbito actual
		this.PunteroAmbitoActual = ListaAmbitos.size() - 1;
	}
	
	public Object obtenerPalRes(Object contenido){
		return Palabras_Reservadas.get(contenido);
		
	}
	
	/**
	 * Closes current scope range and returns to its parent.
	 * @see #AbreAmbito()
	 */
	public void CierraAmbito(){
		// Actualiza el puntero a la tabla actual
		this.PunteroAmbitoActual = TablaOrganizador.get(this.PunteroAmbitoActual);
		/* TODO Habría que ver si la tabla de ámbito previa se puede eliminar
		 * o no para ahorrar costes en espacio.
		 */
	}
	
	/**
	 * Returns private flag to allow insertions.
	 * 
	 * @return true if insertions are allowed, false otherwise
	 * @see #getInsercionesPermitidas()
	 */
	public boolean getInsercionesPermitidas(){
		return this.blnInsercionesPermitidas;
	}
	
	/**
	 * Sets private flag to allow insertions.
	 * 
	 * @param nuevoValor new flag value
	 * @see #getInsercionesPermitidas()
	 */
	public void setInsercionesPermitidas(boolean nuevoValor){
		this.blnInsercionesPermitidas = nuevoValor;
	}
	
}
